/*
* 
* An XmlReader implementation for loading SGML (including HTML) converting it
* to well formed XML, by adding missing quotes, empty attribute values, ignoring
* duplicate attributes, case folding on tag names, adding missing closing tags
* based on SGML DTD information, and so on.
*
* Copyright (c) 2002 Microsoft Corporation. All rights reserved.
*
* Chris Lovett
* 
*/

// Orginal code taken from http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=B90FDDCE-E60D-43F8-A5C4-C3BD760564BC

using System;
using System.Xml;
using System.IO;
using System.Collections;
using System.Text;
using System.Reflection;

namespace Pegasus.Web.Html
{

    /// <summary>
    /// SGML is case insensitive, so here you can choose between converting
    /// to lower case or upper case tags.  "None" means that the case is left
    /// alone, except that end tags will be folded to match the start tags.
    /// </summary>
    public enum CaseFolding {

		/// <summary>
		/// 
		/// </summary>
		None,

		/// <summary>
		/// 
		/// </summary>
		ToUpper,

		/// <summary>
		/// 
		/// </summary>
		ToLower
    }

    /// <summary>
    /// This stack maintains a high water mark for allocated objects so the client
    /// can reuse the objects in the stack to reduce memory allocations, this is
    /// used to maintain current state of the parser for element stack, and attributes
    /// in each element.
    /// </summary>
    internal class HWStack {
        object[] items;
        int size;
        int count;
        int growth;

        public HWStack(int growth) {
            this.growth = growth;
        }

        public int Count {
            get { return this.count; }
            set { this.count = value; }
        }
        public int Size {
            get { return this.size; }
        }
        // returns the item at the requested index or null if index is out of bounds
        public object this[int i] {
            get { return (i>=0 && i < this.size) ? items[i] : null; }
            set { this.items[i] = value; }
        }
        public object Pop(){
            this.count--;
            if (this.count>0){
                return items[this.count-1];
            }
            return null;
        }
        // This method tries to reuse a slot, if it returns null then
        // the user has to call the other Push method.
        public object Push(){
            if (this.count == this.size){
                int newsize = this.size+this.growth;
                object[] newarray = new object[newsize];
                if (this.items != null)
                    Array.Copy(this.items, newarray, this.size);
                this.size = newsize;
                this.items = newarray;
            }
            return items[this.count++];
        }        
        public void RemoveAt(int i){
            this.items[i] = null;
            Array.Copy(this.items, i+1, this.items, i, this.count - i - 1);
            this.count--;

        }
    }

    /// <summary>
    /// This class represents an attribute.  The AttDef is assigned
    /// from a validation process, and is used to provide default values.
    /// </summary>
    internal class Attribute {
        internal string Name;    // the atomized name (using XmlNameTable).
        internal AttDef DtdType; // the AttDef of the attribute from the SGML DTD.
        internal char QuoteChar; // the quote character used for the attribute value.
        internal string literalValue; // tha attribute value

        /// <summary>
        /// Attribute objects are reused during parsing to reduce memory allocations, 
        /// hence the Reset method. 
        /// </summary>
        public void Reset(string name, string value, char quote) {
            this.Name = name;
            this.literalValue = value;
            this.QuoteChar = quote;
            this.DtdType = null;
        }

        public string Value {
            get {
                if (this.literalValue != null) 
                    return this.literalValue;
                if (this.DtdType != null) 
                    return this.DtdType.Default;
                return null;
            }
            set {
                this.literalValue = value;
            }
        }

        public bool IsDefault {
            get {
                return (this.literalValue == null);
            }
        }
    }    

    /// <summary>
    /// This class models an XML node, an array of elements in scope is maintained while parsing
    /// for validation purposes, and these Node objects are reused to reduce object allocation,
    /// hence the reset method.  
    /// </summary>
    internal class Node {
        internal XmlNodeType NodeType;
        internal string Value;
        internal XmlSpace Space;
        internal string XmlLang;
        internal bool IsEmpty;        
        internal string Name;
        internal ElementDecl DtdType; // the DTD type found via validation
        internal State CurrentState;
        internal bool Simulated; // tag was injected into result stream.
        HWStack attributes = new HWStack(10);

        /// <summary>
        /// Attribute objects are reused during parsing to reduce memory allocations, 
        /// hence the Reset method. 
        /// </summary>
        public void Reset(string name, XmlNodeType nt, string value) {           
            this.Value = value;
            this.Name = name;
            this.NodeType = nt;
            this.Space = XmlSpace.None;
            this.XmlLang= null;
            this.IsEmpty = true;
            this.attributes.Count = 0;
            this.DtdType = null;
        }

        public Attribute AddAttribute(string name, string value, char quotechar, bool caseInsensitive) {
            Attribute a;
            // check for duplicates!
            for (int i = 0, n = this.attributes.Count; i < n; i++) {
                a = (Attribute)this.attributes[i];             
                if (caseInsensitive && string.Compare(a.Name, name, true) == 0) {
                    return null;
                } else if ((object)a.Name == (object)name) {
                    return null; 
                }
            }
            // This code makes use of the high water mark for attribute objects,
            // and reuses exisint Attribute objects to avoid memory allocation.
            a = (Attribute)this.attributes.Push();
            if (a == null) {
                a = new Attribute();
                this.attributes[this.attributes.Count-1] = a;
            }
            a.Reset(name, value, quotechar);
            return a;
        }

        public void RemoveAttribute(string name) {
            for (int i = 0, n = this.attributes.Count; i < n; i++) {
                Attribute a  = (Attribute)this.attributes[i];
                if (a.Name == name) {
                    this.attributes.RemoveAt(i);
                    return;
                }
            }
        }
        public void CopyAttributes(Node n) {
            for (int i = 0, len = n.attributes.Count; i < len; i++) {
                Attribute a = (Attribute)n.attributes[i];
                Attribute na = this.AddAttribute(a.Name, a.Value, a.QuoteChar, false);
                na.DtdType = a.DtdType;
            }
        }

        public int AttributeCount {
            get {
                return this.attributes.Count;
            }
        }

        public int GetAttribute(string name) {
            for (int i = 0, n = this.attributes.Count; i < n; i++) {
                Attribute a = (Attribute)this.attributes[i];
                if (a.Name == name) {
                    return i;
                }
            }
            return -1;
        }

        public Attribute GetAttribute(int i) {
            if (i>=0 && i<this.attributes.Count) {
                Attribute a = (Attribute)this.attributes[i];
                return a;
            }
            return null;
        }
    }

    // This enum is used to track the current state of te SgmlReader
    internal enum State {
        Initial,    // The initial state (Read has not been called yet)
        Markup,     // Expecting text or markup
        EndTag,     // Positioned on an end tag
        Attr,       // Positioned on an attribute
        AttrValue,  // Positioned in an attribute value
        Text,       // Positioned on a Text node.
        PartialTag, // Positioned on a text node, and we have hit a start tag
        AutoClose,  // We are auto-closing tags (this is like State.EndTag), but end tag was generated
        CData,      // We are on a CDATA type node, eg. <scipt> where we have special parsing rules.
        PartialText,
        PseudoStartTag, // we pushed a pseudo-start tag, need to continue with previous start tag.
        Eof
    }


    /// <summary>
    /// SgmlReader is an XmlReader API over any SGML document (including built in 
    /// support for HTML).  
    /// </summary>
    public class SgmlReader : XmlReader {
        SgmlDtd dtd;
        Entity current;
        State state;
        XmlNameTable nametable;
        char partial;
        object endTag;
        HWStack stack;
        Node node; // current node (except for attributes)
        // Attributes are handled separately using these members.
        Attribute a;
        int apos; // which attribute are we positioned on in the collection.
        Uri baseUri;
        StringBuilder sb;
        StringBuilder name;
        TextWriter log;
        bool foundRoot;

        // autoclose support
        Node newnode;
        int poptodepth;
        int rootCount;
        bool isHtml;
        string rootElementName;

        string href;
        string errorLogFile;
        Entity lastError;
        string proxy;
        TextReader inputStream;
        string syslit;
        string pubid;
        string subset;
        string docType;
        WhitespaceHandling whitespaceHandling;
        CaseFolding folding = CaseFolding.None;
        bool stripDocType = true;      
        string startTag = null;

		/// <summary>
		/// Initializes a new instance of the <see cref="SgmlReader"/> class.
		/// </summary>
        public SgmlReader() {
            Init();    
            this.nametable = new NameTable();
        }

        /// <summary>
        /// Specify the SgmlDtd object directly.  This allows you to cache the Dtd and share
        /// it across multipl SgmlReaders.  To load a DTD from a URL use the SystemLiteral property.
        /// </summary>
        public SgmlDtd Dtd {
            get { 
                LazyLoadDtd(this.baseUri);
                return this.dtd; 
            }
            set { this.dtd = value; }
        }

        private void LazyLoadDtd(Uri baseUri) {
            if (this.dtd == null) {
                if (this.syslit == null || this.syslit == "") {
                    if (this.docType != null && StringUtilities.EqualsIgnoreCase(this.docType, "html")) {
                        Assembly a = typeof(SgmlReader).Assembly;
                        string name = a.FullName.Split(',')[0]+".Html.dtd";
                        Stream stm = a.GetManifestResourceStream(name);
                        if (stm != null){
                            StreamReader sr = new StreamReader(stm);
                            this.dtd = SgmlDtd.Parse(baseUri, "HTML", null, sr, null, this.proxy, this.nametable);
                        }
                    }
                } else { 
                    if (baseUri != null) {
                        baseUri = new Uri(baseUri, this.syslit);
                    } else if (this.baseUri != null) {
                        baseUri = new Uri(this.baseUri, this.syslit);
                    } else {
                        baseUri = new Uri(new Uri(Directory.GetCurrentDirectory()+"\\"), this.syslit);
                    }
                    this.dtd = SgmlDtd.Parse(baseUri, this.docType, this.pubid, baseUri.AbsoluteUri, this.subset, this.proxy, this.nametable);
                }

                if (this.dtd != null && this.dtd.Name != null){
                    switch (this.CaseFolding){
                        case CaseFolding.ToUpper:
                            this.rootElementName = this.dtd.Name.ToUpper();
                            break;
                        case CaseFolding.ToLower:
                            this.rootElementName = this.dtd.Name.ToLower();
                            break;
                        default:
                            this.rootElementName = this.dtd.Name;
                            break;
                    }
                    this.isHtml = StringUtilities.EqualsIgnoreCase(this.dtd.Name, "html");
                }

            }
        }

        /// <summary>
        /// The name of root element specified in the DOCTYPE tag.
        /// </summary>
        public string DocType {
            get { return this.docType; }
            set { this.docType = value; }
        }

        /// <summary>
        /// The PUBLIC identifier in the DOCTYPE tag
        /// </summary>
        public string PublicIdentifier {
            get { return this.pubid; }
            set { this.pubid = value; }
        }

        /// <summary>
        /// The SYSTEM literal in the DOCTYPE tag identifying the location of the DTD.
        /// </summary>
        public string SystemLiteral {
            get { return this.syslit; }
            set { this.syslit = value; }
        }

        /// <summary>
        /// The DTD internal subset in the DOCTYPE tag
        /// </summary>
        public string InternalSubset {
            get { return this.subset; }
            set { this.subset = value; }
        }

        /// <summary>
        /// The input stream containing SGML data to parse.
        /// You must specify this property or the Href property before calling Read().
        /// </summary>
        public TextReader InputStream {
            get { return this.inputStream; }
            set { this.inputStream = value; Init();}
        }

        /// <summary>
        /// Sometimes you need to specify a proxy server in order to load data via HTTP
        /// from outside the firewall.  For example: "itgproxy:80".
        /// </summary>
        public string WebProxy {
            get { return this.proxy; }
            set { this.proxy = value; }
        }

        /// <summary>
        /// The base Uri is used to resolve relative Uri's like the SystemLiteral and
        /// Href properties.  This is a method because BaseURI is a read-only
        /// property on the base XmlReader class.
        /// </summary>
        public void SetBaseUri(string uri)  {
            this.baseUri = new Uri(uri);
        }

        /// <summary>
        /// Specify the location of the input SGML document as a URL.
        /// </summary>
        public string Href {
            get { return this.href; }
            set { this.href = value; 
                Init();
                if (this.baseUri == null) {
                    if (this.href.IndexOf("://")>0) {
                        this.baseUri = new Uri(this.href);
                    } else {
                        this.baseUri = new Uri("file:///"+Directory.GetCurrentDirectory()+"//");
                    }
                }
            }
        }

        /// <summary>
        /// Whether to strip out the DOCTYPE tag from the output (default true)
        /// </summary>
        public bool StripDocType {
            get { return this.stripDocType; }
            set { this.stripDocType = value; }
        }

		/// <summary>
		/// Gets or sets the case folding.
		/// </summary>
		/// <value>The case folding.</value>
        public CaseFolding CaseFolding {
            get { return this.folding; }
            set { this.folding = value; }
        }

        /// <summary>
        /// DTD validation errors are written to this stream.
        /// </summary>
        public TextWriter ErrorLog {
            get { return this.log; }
            set { this.log = value; }
        }

        /// <summary>
        /// DTD validation errors are written to this log file.
        /// </summary>
        public string ErrorLogFile {
            get { return this.errorLogFile; }
            set { this.errorLogFile = value; 
                this.ErrorLog = new StreamWriter(value); }
        }

        void Log(string msg, params string[] args) {
            if (ErrorLog != null) {
                string err = String.Format(msg, args);
                if (this.lastError != this.current) {
                    err = err + "    " + this.current.Context();
                    this.lastError = this.current;
                    ErrorLog.WriteLine("### Error:"+err);
                } else {
                    string path = "";
                    if (this.current.ResolvedUri != null) {
                        path = this.current.ResolvedUri.AbsolutePath;
                    }
                    ErrorLog.WriteLine("### Error in "+
                        path+"#"+
                        this.current.Name+
                        ", line "+this.current.Line + ", position " + this.current.LinePosition + ": "+
                        err);
                }
            }
        }
        void Log(string msg, char ch) {
            Log(msg, ch.ToString());
        }


        void Init() {
            this.state = State.Initial;
            this.stack = new HWStack(10);
            this.node = Push(null, XmlNodeType.Document, null);
            this.node.IsEmpty = false;
            this.sb = new StringBuilder();
            this.name = new StringBuilder();
            this.poptodepth = 0;
            this.current = null;
            this.partial = '\0';
            this.endTag = null;
            this.a = null;
            this.apos = 0;
            this.newnode = null;
            this.rootCount = 0;
            this.foundRoot = false;
        }

        Node Push(string name, XmlNodeType nt, string value) {
            Node result = (Node)this.stack.Push();
            if (result == null) {
                result = new Node();
                this.stack[this.stack.Count-1] = result;
            }
            result.Reset(name, nt, value);
            this.node = result;
            return result;
        }

        void SwapTopNodes() {
            int top = this.stack.Count-1;
            if (top > 0) {
                Node n = (Node)this.stack[top - 1];
                this.stack[top - 1] = this.stack[top];
                this.stack[top] = n;
            }
        }

        Node Push(Node n) {
            // we have to do a deep clone of the Node object because
            // it is reused in the stack.
            Node n2 = Push(n.Name, n.NodeType, n.Value);
            n2.DtdType = n.DtdType;
            n2.IsEmpty = n.IsEmpty;
            n2.Space = n.Space;
            n2.XmlLang = n.XmlLang;
            n2.CurrentState = n.CurrentState;
            n2.CopyAttributes(n);
            this.node = n2;
            return n2;
        }

        void Pop() {
            if (this.stack.Count > 1) {
                this.node = (Node)this.stack.Pop();
            }
        }

        Node Top() {
            int top = this.stack.Count - 1;
            if (top > 0) {
                return (Node)this.stack[top];
            }
            return null;
        }

		/// <summary>
		/// When overridden in a derived class, gets the type of the current node.
		/// </summary>
		/// <value></value>
		/// <returns>One of the <see cref="T:System.Xml.XmlNodeType"></see> values representing the type of the current node.</returns>
        public override XmlNodeType NodeType {
            get { 
                if (this.state == State.Attr) {
                    return XmlNodeType.Attribute;
                } 
                else if (this.state == State.AttrValue) {
                    return XmlNodeType.Text;
                }
                else if (this.state == State.EndTag || this.state == State.AutoClose) {
                    return XmlNodeType.EndElement;
                }
                return this.node.NodeType;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the qualified name of the current node.
		/// </summary>
		/// <value></value>
		/// <returns>The qualified name of the current node. For example, Name is bk:book for the element &lt;bk:book&gt;.The name returned is dependent on the <see cref="P:System.Xml.XmlReader.NodeType"></see> of the node. The following node types return the listed values. All other node types return an empty string.Node type Name AttributeThe name of the attribute. DocumentTypeThe document type name. ElementThe tag name. EntityReferenceThe name of the entity referenced. ProcessingInstructionThe target of the processing instruction. XmlDeclarationThe literal string xml. </returns>
        public override string Name {
            get {
                return this.LocalName;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the local name of the current node.
		/// </summary>
		/// <value></value>
		/// <returns>The name of the current node with the prefix removed. For example, LocalName is book for the element &lt;bk:book&gt;.For node types that do not have a name (like Text, Comment, and so on), this property returns String.Empty.</returns>
        public override string LocalName { 
            get {
                string result = null;
                if (this.state == State.Attr) {
                    result = this.a.Name;
                } 
                else if (this.state == State.AttrValue) {
                    result = null;
                }
                else {
                    result = this.node.Name;
                }

                return result;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the namespace URI (as defined in the W3C Namespace specification) of the node on which the reader is positioned.
		/// </summary>
		/// <value></value>
		/// <returns>The namespace URI of the current node; otherwise an empty string.</returns>
        public override string NamespaceURI { 
            get {
                // SGML has no namespaces, unless this turned out to be an xmlns attribute.
                if (this.state == State.Attr && StringUtilities.EqualsIgnoreCase(this.a.Name, "xmlns")) {
                    return "http://www.w3.org/2000/xmlns/";
                }
                return String.Empty;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the namespace prefix associated with the current node.
		/// </summary>
		/// <value></value>
		/// <returns>The namespace prefix associated with the current node.</returns>
        public override string Prefix { 
            get {
                // SGML has no namespaces.
                return String.Empty;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets a value indicating whether the current node can have a <see cref="P:System.Xml.XmlReader.Value"></see>.
		/// </summary>
		/// <value></value>
		/// <returns>true if the node on which the reader is currently positioned can have a Value; otherwise, false. If false, the node has a value of String.Empty.</returns>
        public override bool HasValue { 
            get {
                if (this.state == State.Attr || this.state == State.AttrValue) {
                    return true;
                }
                return (this.node.Value != null);
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the text value of the current node.
		/// </summary>
		/// <value></value>
		/// <returns>The value returned depends on the <see cref="P:System.Xml.XmlReader.NodeType"></see> of the node. The following table lists node types that have a value to return. All other node types return String.Empty.Node type Value AttributeThe value of the attribute. CDATAThe content of the CDATA section. CommentThe content of the comment. DocumentTypeThe internal subset. ProcessingInstructionThe entire content, excluding the target. SignificantWhitespaceThe white space between markup in a mixed content model. TextThe content of the text node. WhitespaceThe white space between markup. XmlDeclarationThe content of the declaration. </returns>
        public override string Value { 
            get {
                if (this.state == State.Attr || this.state == State.AttrValue) {
                    return this.a.Value;
                }
                return this.node.Value;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the depth of the current node in the XML document.
		/// </summary>
		/// <value></value>
		/// <returns>The depth of the current node in the XML document.</returns>
        public override int Depth { 
            get {
                if (this.state == State.Attr) {
                    return this.stack.Count;
                } 
                else if (this.state == State.AttrValue) {
                    return this.stack.Count+1;
                }
                return this.stack.Count-1;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the base URI of the current node.
		/// </summary>
		/// <value></value>
		/// <returns>The base URI of the current node.</returns>
        public override string BaseURI { 
            get {
                return this.baseUri == null ? "" : this.baseUri.AbsoluteUri;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets a value indicating whether the current node is an empty element (for example, &lt;MyElement/&gt;).
		/// </summary>
		/// <value></value>
		/// <returns>true if the current node is an element (<see cref="P:System.Xml.XmlReader.NodeType"></see> equals XmlNodeType.Element) that ends with /&gt;; otherwise, false.</returns>
        public override bool IsEmptyElement { 
            get {
                if (this.state == State.Markup || this.state == State.Attr || this.state == State.AttrValue) {
                    return this.node.IsEmpty;
                }
                return false;
            }
        }
		/// <summary>
		/// When overridden in a derived class, gets a value indicating whether the current node is an attribute that was generated from the default value defined in the DTD or schema.
		/// </summary>
		/// <value></value>
		/// <returns>true if the current node is an attribute whose value was generated from the default value defined in the DTD or schema; false if the attribute value was explicitly set.</returns>
        public override bool IsDefault { 
            get {
                if (this.state == State.Attr || this.state == State.AttrValue) 
                    return this.a.IsDefault;
                return false;
            }
        }
		/// <summary>
		/// When overridden in a derived class, gets the quotation mark character used to enclose the value of an attribute node.
		/// </summary>
		/// <value></value>
		/// <returns>The quotation mark character (" or ') used to enclose the value of an attribute node.</returns>
        public override char QuoteChar { 
            get {
                if (this.a != null) return this.a.QuoteChar;
                return '\0';
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the current xml:space scope.
		/// </summary>
		/// <value></value>
		/// <returns>One of the <see cref="T:System.Xml.XmlSpace"></see> values. If no xml:space scope exists, this property defaults to XmlSpace.None.</returns>
        public override XmlSpace XmlSpace { 
            get {
                for (int i = this.stack.Count-1; i > 1; i--) {
                    Node n = (Node)this.stack[i];
                    XmlSpace xs = n.Space;
                    if (xs != XmlSpace.None) return xs;
                }
                return XmlSpace.None;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the current xml:lang scope.
		/// </summary>
		/// <value></value>
		/// <returns>The current xml:lang scope.</returns>
        public override string XmlLang { 
            get {
                for (int i = this.stack.Count-1; i > 1; i--) {
                    Node n = (Node)this.stack[i];
                    string xmllang = n.XmlLang;
                    if (xmllang != null) return xmllang;
                }
                return String.Empty;
            }
        }

		/// <summary>
		/// Gets or sets the whitespace handling.
		/// </summary>
		/// <value>The whitespace handling.</value>
        public WhitespaceHandling WhitespaceHandling {
            get {
                return this.whitespaceHandling;
            } 
            set {
                this.whitespaceHandling = value;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the number of attributes on the current node.
		/// </summary>
		/// <value></value>
		/// <returns>The number of attributes on the current node.</returns>
        public override int AttributeCount { 
            get {
                if (this.state == State.Attr || this.state == State.AttrValue) 
                    return 0;
                if (this.node.NodeType == XmlNodeType.Element ||
                    this.node.NodeType == XmlNodeType.DocumentType)
                    return this.node.AttributeCount;
                return 0;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the value of the attribute with the specified <see cref="P:System.Xml.XmlReader.Name"></see>.
		/// </summary>
		/// <param name="name">The qualified name of the attribute.</param>
		/// <returns>
		/// The value of the specified attribute. If the attribute is not found, null is returned.
		/// </returns>
        public override string GetAttribute(string name) {
            if (this.state != State.Attr && this.state != State.AttrValue) {
                int i = this.node.GetAttribute(name);
                if (i>=0) return GetAttribute(i);
            }
            return null;
        }

		/// <summary>
		/// When overridden in a derived class, gets the value of the attribute with the specified <see cref="P:System.Xml.XmlReader.LocalName"></see> and <see cref="P:System.Xml.XmlReader.NamespaceURI"></see>.
		/// </summary>
		/// <param name="name">The local name of the attribute.</param>
		/// <param name="namespaceURI">The namespace URI of the attribute.</param>
		/// <returns>
		/// The value of the specified attribute. If the attribute is not found, null is returned. This method does not move the reader.
		/// </returns>
        public override string GetAttribute(string name, string namespaceURI) {
            return GetAttribute(name); // SGML has no namespaces.
        }

		/// <summary>
		/// When overridden in a derived class, gets the value of the attribute with the specified index.
		/// </summary>
		/// <param name="i">The index of the attribute. The index is zero-based. (The first attribute has index 0.)</param>
		/// <returns>
		/// The value of the specified attribute. This method does not move the reader.
		/// </returns>
        public override string GetAttribute(int i) {
            if (this.state != State.Attr && this.state != State.AttrValue) {
                Attribute a = this.node.GetAttribute(i);
                if (a != null)
                    return a.Value;
            }
            throw new IndexOutOfRangeException();
        }

		/// <summary>
		/// Gets the <see cref="System.String"/> with the specified i.
		/// </summary>
		/// <value></value>
        public override string this [ int i ] { 
            get {
                return GetAttribute(i);
            }
        }

		/// <summary>
		/// Gets the <see cref="System.String"/> with the specified name.
		/// </summary>
		/// <value></value>
        public override string this [ string name ] { 
            get {
                return GetAttribute(name);
            }
        }

		/// <summary>
		/// Gets the <see cref="System.String"/> with the specified name.
		/// </summary>
		/// <value></value>
        public override string this [ string name,string namespaceURI ] { 
            get {
                return GetAttribute(name, namespaceURI);
            }
        }

		/// <summary>
		/// When overridden in a derived class, moves to the attribute with the specified <see cref="P:System.Xml.XmlReader.Name"></see>.
		/// </summary>
		/// <param name="name">The qualified name of the attribute.</param>
		/// <returns>
		/// true if the attribute is found; otherwise, false. If false, the reader's position does not change.
		/// </returns>
        public override bool MoveToAttribute(string name) {
            int i = this.node.GetAttribute(name);
            if (i>=0) {
                MoveToAttribute(i);
                return true;
            }
            return false;
        }

		/// <summary>
		/// When overridden in a derived class, moves to the attribute with the specified <see cref="P:System.Xml.XmlReader.LocalName"></see> and <see cref="P:System.Xml.XmlReader.NamespaceURI"></see>.
		/// </summary>
		/// <param name="name">The local name of the attribute.</param>
		/// <param name="ns">The namespace URI of the attribute.</param>
		/// <returns>
		/// true if the attribute is found; otherwise, false. If false, the reader's position does not change.
		/// </returns>
        public override bool MoveToAttribute(string name, string ns) {
            return MoveToAttribute(name);
        }

		/// <summary>
		/// When overridden in a derived class, moves to the attribute with the specified index.
		/// </summary>
		/// <param name="i">The index of the attribute.</param>
        public override void MoveToAttribute(int i) {
            Attribute a = this.node.GetAttribute(i);
            if (a != null) {
                this.apos = i;
                this.a = a;
                if (this.state != State.Attr) {
                    this.node.CurrentState = this.state;//save current state.
                }
                this.state = State.Attr;
                return;
            }
            throw new IndexOutOfRangeException();
        }

		/// <summary>
		/// When overridden in a derived class, moves to the first attribute.
		/// </summary>
		/// <returns>
		/// true if an attribute exists (the reader moves to the first attribute); otherwise, false (the position of the reader does not change).
		/// </returns>
        public override bool MoveToFirstAttribute() {
            if (this.node.AttributeCount>0) {
                MoveToAttribute(0);
                return true;
            }
            return false;
        }

		/// <summary>
		/// When overridden in a derived class, moves to the next attribute.
		/// </summary>
		/// <returns>
		/// true if there is a next attribute; false if there are no more attributes.
		/// </returns>
        public override bool MoveToNextAttribute() {
            if (this.state != State.Attr && this.state != State.AttrValue) {
                return MoveToFirstAttribute();
            }
            if (this.apos<this.node.AttributeCount-1) {
                MoveToAttribute(this.apos+1);
                return true;
            }
            return false;
        }

		/// <summary>
		/// When overridden in a derived class, moves to the element that contains the current attribute node.
		/// </summary>
		/// <returns>
		/// true if the reader is positioned on an attribute (the reader moves to the element that owns the attribute); false if the reader is not positioned on an attribute (the position of the reader does not change).
		/// </returns>
        public override bool MoveToElement() {
            if (this.state == State.Attr || this.state == State.AttrValue) {
                this.state = this.node.CurrentState;
                this.a = null;
                return true;
            }
            return (this.node.NodeType == XmlNodeType.Element);
        }

        bool IsHtml {
            get {
              return this.isHtml;
            }
        }

		/// <summary>
		/// Gets the encoding.
		/// </summary>
		/// <returns></returns>
        public Encoding GetEncoding(){
            if (this.current == null) {
                OpenInput();
            }
            return this.current.GetEncoding();
        }

        void OpenInput(){
            LazyLoadDtd(this.baseUri);

            if (this.Href != null) {
                this.current = new Entity("#document", null, this.href, this.proxy);
            } else if (this.inputStream != null) {
                this.current = new Entity("#document", null, this.inputStream, this.proxy);           
            } else {
                throw new InvalidOperationException("You must specify input either via Href or InputStream properties");
            }
            this.current.Html = this.IsHtml;
            this.current.Open(null, this.baseUri);
            if (this.current.ResolvedUri != null)
                this.baseUri = this.current.ResolvedUri;

            if (this.current.Html && this.dtd == null){
                this.docType = "HTML";
                LazyLoadDtd(this.baseUri);
            }
        }

		/// <summary>
		/// When overridden in a derived class, reads the next node from the stream.
		/// </summary>
		/// <returns>
		/// true if the next node was read successfully; false if there are no more nodes to read.
		/// </returns>
		/// <exception cref="T:System.Xml.XmlException">An error occurred while parsing the XML. </exception>
        public override bool Read() {
            if (current == null) {
                OpenInput();
            }
            State start = this.state;
            if (node.Simulated) {
                // return the next node
                node.Simulated = false;
                this.node = Top();
                this.state = this.node.CurrentState;
                return true;
            }

            bool foundnode = false;
            while (! foundnode) {
                switch (this.state) {
                    case State.Initial:
                        this.state = State.Markup;
                        this.current.ReadChar();
                        goto case State.Markup;
                    case State.Eof:
                        if (this.current.Parent != null) {
                            this.current.Close();
                            this.current = this.current.Parent;
                        } else {                           
                            return false;
                        }
                        break;
                    case State.EndTag:
                        if (this.endTag == (object)this.node.Name) {
                            Pop(); // we're done!
                            this.state = State.Markup;
                            goto case State.Markup;
                        }                     
                        Pop(); // close one element
                        foundnode = true;// return another end element.
                        break;
                    case State.Markup:
                        if (this.node.IsEmpty) {
                            Pop();
                        }
                        Node n = this.node;
                        foundnode = ParseMarkup();
                        break;
                    case State.PartialTag:
                        Pop(); // remove text node.
                        this.state = State.Markup;
                        foundnode = ParseTag(this.partial);
                        break;
                    case State.PseudoStartTag:
                        foundnode = ParseStartTag('<');                        
                        break;
                    case State.AutoClose:
                        Pop(); // close next node.
                        if (this.stack.Count <= this.poptodepth) {
                            this.state = State.Markup;
                            if (this.newnode != null) {
                                Push(this.newnode); // now we're ready to start the new node.
                                this.newnode = null;
                                this.state = State.Markup;
                            } else if (this.node.NodeType == XmlNodeType.Document) {
                                this.state = State.Eof;
                                goto case State.Eof;
                            }
                        } 
                        foundnode = true;
                        break;
                    case State.CData:
                        foundnode = ParseCData();
                        break;
                    case State.Attr:
                        goto case State.AttrValue;
                    case State.AttrValue:
                        this.state = State.Markup;
                        goto case State.Markup;
                    case State.Text:
                        Pop();
                        goto case State.Markup;
                    case State.PartialText:
                        if (ParseText(this.current.Lastchar, false)) {
                            this.node.NodeType = XmlNodeType.Whitespace;
                        }
                        foundnode = true;
                        break;
                }
                if (foundnode && this.node.NodeType == XmlNodeType.Whitespace && this.whitespaceHandling == WhitespaceHandling.None) {
                    // strip out whitespace (caller is probably pretty printing the XML).
                    foundnode = false;
                }
                if (!foundnode && this.state == State.Eof && this.stack.Count>1) {
                    this.poptodepth = 1;
                    state = State.AutoClose;
                    this.node = Top();
                    return true;
                }
            }
            if (!foundRoot && (this.NodeType == XmlNodeType.Element ||
                    this.NodeType == XmlNodeType.Text ||
                    this.NodeType == XmlNodeType.CDATA)) {
                foundRoot = true;
                if (this.IsHtml && (this.NodeType != XmlNodeType.Element ||
                    string.Compare(this.LocalName, "html", true, System.Globalization.CultureInfo.InvariantCulture) != 0)) {
                    // Simulate an HTML root element!
                    this.node.CurrentState = this.state;
                    Node root = Push("html", XmlNodeType.Element, null);
                    SwapTopNodes(); // make html the outer element.
                    this.node = root;
                    root.Simulated = true;
                    root.IsEmpty = false;
                    this.state = State.Markup;
                    //this.state = State.PseudoStartTag;
                    //this.startTag = name;
                }
                return true;
            }
            return true;
        }

        bool ParseMarkup() {
            char ch = this.current.Lastchar;
            if (ch == '<') {
                ch = this.current.ReadChar();
                return ParseTag(ch);
            } 
            else if (ch != Entity.EOF) {
                if (this.node.DtdType != null && this.node.DtdType.ContentModel.DeclaredContent == DeclaredContent.CDATA) {
                    // e.g. SCRIPT or STYLE tags which contain unparsed character data.
                    this.partial = '\0';
                    this.state = State.CData;
                    return false;
                }
                else if (ParseText(ch, true)) {
                    this.node.NodeType = XmlNodeType.Whitespace;
                }
                return true;
            }
            this.state = State.Eof;
            return false;
        }

        static string declterm = " \t\r\n><";
        bool ParseTag(char ch) {
            if (ch == '%') {
                return ParseAspNet();
            }
            if (ch == '!') {
                ch = this.current.ReadChar();
                if (ch == '-') {
                    return ParseComment();
                } else if (ch == '[') {
                    return ParseConditionalBlock();
                }else if (ch != '_' && !Char.IsLetter(ch)) {
                    // perhaps it's one of those nasty office document hacks like '<![if ! ie ]>'
                    string value = this.current.ScanToEnd(this.sb, "Recovering", ">"); // skip it
                    Log("Ignoring invalid markup '<!"+value+">");
                    return false;
                }
                else {
                    string name = this.current.ScanToken(this.sb, SgmlReader.declterm, false);
                    if (name == "DOCTYPE") {
                        ParseDocType();
                        // In SGML DOCTYPE SYSTEM attribute is optional, but in XML it is required,
                        // therefore if there is no SYSTEM literal then add an empty one.
                        if (this.GetAttribute("SYSTEM") == null && this.GetAttribute("PUBLIC") != null) {
                            this.node.AddAttribute("SYSTEM", "", '"', this.folding == CaseFolding.None);
                        }
                        if (stripDocType) {
                            return false;
                        } else {
                            this.node.NodeType = XmlNodeType.DocumentType;
                            return true;
                        }
                    } 
                    else {
                        Log("Invalid declaration '<!{0}...'.  Expecting '<!DOCTYPE' only.", name);
                        this.current.ScanToEnd(null, "Recovering", ">"); // skip it
                        return false;
                    }
                }
            } 
            else if (ch == '?') {
                this.current.ReadChar();// consume the '?' character.
                return ParsePI();
            }
            else if (ch == '/') {
                return ParseEndTag();
            }
            else {
                return ParseStartTag(ch);
            }
//            return true;
        }

        string ScanName(string terminators) {
            string name = this.current.ScanToken(this.sb, terminators, false);
            switch (this.folding){
                case CaseFolding.ToUpper:
                    name = name.ToUpper();
                    break;
                case CaseFolding.ToLower:
                    name = name.ToLower();
                    break;
            }
            return this.nametable.Add(name);
        }

        static string tagterm = " \t\r\n=/><";
        static string aterm = " \t\r\n='\"/>";
        static string avterm = " \t\r\n>";
        bool ParseStartTag(char ch) {
            string name = null;
            if (state != State.PseudoStartTag){
                if (SgmlReader.tagterm.IndexOf(ch)>=0) {
                    this.sb.Length = 0;
                    this.sb.Append('<');
                    this.state = State.PartialText;
                    return false;
                }
                name = ScanName(SgmlReader.tagterm);                
            } else {
                name = this.startTag;
                state = State.Markup;
            }
            Node n = Push(name, XmlNodeType.Element, null);
            n.IsEmpty = false;
            Validate(n);
            ch = this.current.SkipWhitespace();
            while (ch != Entity.EOF && ch != '>') {
                if (ch == '/') {
                    n.IsEmpty = true;
                    ch = this.current.ReadChar();
                    if (ch != '>') {
                        Log("Expected empty start tag '/>' sequence instead of '{0}'", ch);
                        this.current.ScanToEnd(null, "Recovering", ">");
                        return false;
                    }
                    break;
                } 
                else if (ch == '<') {
                    Log("Start tag '{0}' is missing '>'", name);
                    break;
                }
                string aname = ScanName(SgmlReader.aterm);
                ch = this.current.SkipWhitespace();
                if (aname == "," || aname == "=" || aname == ":" || aname == ";") {
                    continue;
                }
                string value = null;
                char quote = '\0';
                if (ch == '=' || ch == '"' || ch == '\'') {
                    if (ch == '=' ){
                        this.current.ReadChar();
                        ch = this.current.SkipWhitespace();
                    }
                    if (ch == '\'' || ch == '\"') {
                        quote = ch;
                        value = ScanLiteral(this.sb, ch);
                    } 
                    else if (ch != '>') {
                        string term = SgmlReader.avterm;
                        value = this.current.ScanToken(this.sb, term, false);
                    }
                } 
                if (aname.Length > 0) {
                    Attribute a = n.AddAttribute(aname, value, quote, this.folding == CaseFolding.None);
                    if (a == null) {
                        Log("Duplicate attribute '{0}' ignored", aname);
                    } else {
                        ValidateAttribute(n, a);
                    }
                }
                ch = this.current.SkipWhitespace();
            }
            if (ch == Entity.EOF) {
                this.current.Error("Unexpected EOF parsing start tag '{0}'", name);
            } 
            else if (ch == '>') {
                this.current.ReadChar(); // consume '>'
            }
            if (this.Depth == 1) {
                if (this.rootCount == 1) {
                    // Hmmm, we found another root level tag, soooo, the only
                    // thing we can do to keep this a valid XML document is stop
                    this.state = State.Eof;
                    return false;
                }
                this.rootCount++;
            }
            ValidateContent(n);
            return true;
        }

        bool ParseEndTag() {
            this.state = State.EndTag;
            this.current.ReadChar(); // consume '/' char.
            string name = this.ScanName(SgmlReader.tagterm);
            char ch = this.current.SkipWhitespace();
            if (ch != '>') {
                Log("Expected empty start tag '/>' sequence instead of '{0}'", ch);
                this.current.ScanToEnd(null, "Recovering", ">");
            }
            this.current.ReadChar(); // consume '>'

            this.endTag = name;
            // Make sure there's a matching start tag for it.                        
            bool caseInsensitive = (this.folding == CaseFolding.None);
            this.node = (Node)this.stack[this.stack.Count-1];
            for (int i = this.stack.Count-1; i>0; i--) {
                Node n = (Node)this.stack[i];
                if (caseInsensitive && string.Compare(n.Name, name, true) == 0) {
                    this.endTag = n.Name;
                    return true;
                } else if ((object)n.Name == (object)name) {
                    return true;
                }
            }
            Log("No matching start tag for '</{0}>'", name);
            this.state = State.Markup;
            return false;
        }

        bool ParseAspNet() {
            string value = "<%" + this.current.ScanToEnd(this.sb, "AspNet", "%>") + "%>";
            Push(null, XmlNodeType.CDATA, value);         
            return true;
        }

        bool ParseComment() {
            char ch = this.current.ReadChar();
            if (ch != '-') {
                Log("Expecting comment '<!--' but found {0}", ch);
                this.current.ScanToEnd(null, "Comment", ">");
                return false;
            }
            string value = this.current.ScanToEnd(this.sb, "Comment", "-->");
            
            // Make sure it's a valid comment!
            int i = value.IndexOf("--");
            while (i>=0) {
                int j = i+2;
                while (j<value.Length && value[j]=='-')
                    j++;
                if (i>0) {
                    value = value.Substring(0, i-1)+"-"+value.Substring(j);
                } 
                else {
                    value = "-"+value.Substring(j);
                }
                i = value.IndexOf("--");
            }
            if (value.Length>0 && value[value.Length-1] == '-') {
                value += " "; // '-' cannot be last character
            }
            Push(null, XmlNodeType.Comment, value);         
            return true;
        }

        static string cdataterm = "\t\r\n[<>";
        bool ParseConditionalBlock(){
            char ch = current.ReadChar(); // skip '['
            ch = current.SkipWhitespace();
            string name = current.ScanToken(sb, cdataterm, false);
            if (name != "CDATA"){
                Log("Expecting CDATA but found '{0}'", name);
                current.ScanToEnd(null, "CDATA", ">");
                return false;
            }
            ch = current.SkipWhitespace();
            if (ch != '[') {
                Log("Expecting '[' but found '{0}'", ch);
                current.ScanToEnd(null, "CDATA", ">");
                return false;
            }
            string value = current.ScanToEnd(sb, "CDATA", "]]>");
                        
            Push(null, XmlNodeType.CDATA, value);         
            return true;
        }

        static string dtterm = " \t\r\n>";
        void ParseDocType() {
            char ch = this.current.SkipWhitespace();
            string name = this.ScanName(SgmlReader.dtterm);
            Push(name, XmlNodeType.DocumentType, null);
            ch = this.current.SkipWhitespace();
            if (ch != '>') {
                string subset = "";
                string pubid = "";
                string syslit = "";

                if (ch != '[') {
                    string token = this.current.ScanToken(this.sb, SgmlReader.dtterm, false);
                    if (token == "PUBLIC") {
                        ch = this.current.SkipWhitespace();
                        if (ch == '\"' || ch == '\'') {
                            pubid = this.current.ScanLiteral(this.sb, ch);
                            this.node.AddAttribute(token, pubid, ch, this.folding == CaseFolding.None);  
                        }
                    } 
                    else if (token != "SYSTEM") {
                        Log("Unexpected token in DOCTYPE '{0}'", token);
                        this.current.ScanToEnd(null, "DOCTYPE", ">");
                    }
                    ch = this.current.SkipWhitespace();
                    if (ch == '\"' || ch == '\'') {
                        token = this.nametable.Add("SYSTEM");
                        syslit = this.current.ScanLiteral(this.sb, ch);
                        this.node.AddAttribute(token, syslit, ch, this.folding == CaseFolding.None);  
                    }
                    ch = this.current.SkipWhitespace();
                }
                if (ch == '[') {
                    subset = this.current.ScanToEnd(this.sb, "Internal Subset", "]");
                    this.node.Value = subset;
                }
                ch = this.current.SkipWhitespace();
                if (ch != '>') {
                    Log("Expecting end of DOCTYPE tag, but found '{0}'", ch);
                    this.current.ScanToEnd(null, "DOCTYPE", ">");
                }

                if (this.dtd == null) {
                    this.docType = name;
                    this.pubid = pubid;
                    this.syslit = syslit;
                    this.subset = subset;
                    LazyLoadDtd(this.current.ResolvedUri);
                }
            }           
            this.current.ReadChar();
        }

        static string piterm = " \t\r\n?";
        bool ParsePI() {
            string name = this.current.ScanToken(this.sb, SgmlReader.piterm, false);
            string value = null;
            if (this.current.Lastchar != '?') {
                // Notice this is not "?>".  This is because Office generates bogus PI's that end with "/>".
                value = this.current.ScanToEnd(this.sb, "Processing Instruction", ">");
            }
            else {
                // error recovery.
                value = this.current.ScanToEnd(this.sb, "Processing Instruction", ">");
            }
            // skip xml declarations, since these are generated in the output instead.
            if (name != "xml"){
                Push(nametable.Add(name), XmlNodeType.ProcessingInstruction, value);
                return true;
            }
            return false;
        }

        bool ParseText(char ch, bool newtext) {
            bool ws = !newtext || this.current.IsWhitespace;
            if (newtext) this.sb.Length = 0;
            //this.sb.Append(ch);
            //ch = this.current.ReadChar();
            this.state = State.Text;
            while (ch != Entity.EOF) {
                if (ch == '<') {
                    ch = this.current.ReadChar();
                    if (ch == '/' || ch == '!' || ch == '?' || Char.IsLetter(ch)) {
                        // Hit a tag, so return XmlNodeType.Text token
                        // and remember we partially started a new tag.
                        this.state = State.PartialTag;
                        this.partial = ch;
                        break;
                    } 
                    else {
                        // not a tag, so just proceed.
                        this.sb.Append('<'); 
                        this.sb.Append(ch);
                        ws = false;
                        ch = this.current.ReadChar();
                    }
                } 
                else if (ch == '&') {
                    ExpandEntity(this.sb, '<');
                    ws = false;
                    ch = this.current.Lastchar;
                }
                else {
                    if (!this.current.IsWhitespace) ws = false;
                    this.sb.Append(ch);
                    ch = this.current.ReadChar();
                }
            }
            string value = this.sb.ToString();
            Push(null, XmlNodeType.Text, value);
            return ws;
        }

        // This version is slightly different from Entity.ScanLiteral in that
        // it also expands entities.
		/// <summary>
		/// Scans the literal.
		/// </summary>
		/// <param name="sb">The sb.</param>
		/// <param name="quote">The quote.</param>
		/// <returns></returns>
        public string ScanLiteral(StringBuilder sb, char quote) {
            sb.Length = 0;
            char ch = this.current.ReadChar();
            while (ch != Entity.EOF && ch != quote ) {
                if (ch == '&') {
                    ExpandEntity(this.sb, quote);
                    ch = this.current.Lastchar;
                }               
                else {
                    sb.Append(ch);
                    ch = this.current.ReadChar();
                }
            }
            this.current.ReadChar(); // consume end quote.          
            return sb.ToString();
        }

        bool ParseCData() {
            // Like ParseText(), only it doesn't allow elements in the content.  
            // It allows comments and processing instructions and text only and
            // text is not returned as text but CDATA (since it may contain angle brackets).
            // And initial whitespace is ignored.  It terminates when we hit the
            // end tag for the current CDATA node (e.g. </style>).
            bool ws = this.current.IsWhitespace;
            this.sb.Length = 0;
            char ch = this.current.Lastchar;
            if (this.partial != '\0') {
                Pop(); // pop the CDATA
                switch (this.partial) {
                    case '!':
                        this.partial = ' '; // and pop the comment next time around
                        return ParseComment();
                    case '?':
                        this.partial = ' '; // and pop the PI next time around
                        return ParsePI();
                    case '/':
                        this.state = State.EndTag;
                        return true;    // we are done!
                    case ' ':
                        break; // means we just needed to pop the Comment, PI or CDATA.
                }
            } else {
                ch = this.current.ReadChar();
            }            
            
            // if this.partial == '!' then parse the comment and return
            // if this.partial == '?' then parse the processing instruction and return.            
            while (ch != Entity.EOF) {
                if (ch == '<') {
                    ch = this.current.ReadChar();
                    if (ch == '!') {
                        ch = this.current.ReadChar();
                        if (ch == '-') {
                            // return what CDATA we have accumulated so far
                            // then parse the comment and return to here.
                            if (ws) {
                                this.partial = ' '; // pop comment next time through
                                return ParseComment();
                            } 
                            else {
                                // return what we've accumulated so far then come
                                // back in and parse the comment.
                                this.partial = '!';
                                break; 
                            }
#if FIX
                        } else if (ch == '['){
                            // We are about to wrap this node as a CDATA block because of it's
                            // type in the DTD, but since we found a CDATA block in the input
                            // we have to parse it as a CDATA block, otherwise we will attempt
                            // to output nested CDATA blocks which of course is illegal.
                            if (this.ParseConditionalBlock()){
                                this.partial = ' ';
                                return true;
                            }
#endif
                        } else {
                            // not a comment, so ignore it and continue on.
                            this.sb.Append('<');
                            this.sb.Append('!');
                            this.sb.Append(ch);
                            ws = false;
                        }
                    } 
                    else if (ch == '?') {
                        // processing instruction.
                        this.current.ReadChar();// consume the '?' character.
                        if (ws) {
                            this.partial = ' '; // pop PI next time through
                            return ParsePI();
                        } 
                        else {
                            this.partial = '?';
                            break;
                        }
                    }
                    else if (ch == '/') {
                        // see if this is the end tag for this CDATA node.
                        string temp = this.sb.ToString();
                        if (ParseEndTag() && this.endTag == (object)this.node.Name) {
                            if (ws || temp == "") {
                                // we are done!
                                return true;
                            } 
                            else {
                                // return CDATA text then the end tag
                                this.partial = '/';
                                this.sb.Length = 0; // restore buffer!
                                this.sb.Append(temp); 
                                this.state = State.CData;
                                break;
                            }
                        } 
                        else {
                            // wrong end tag, so continue on.
                            this.sb.Length = 0; // restore buffer!
                            this.sb.Append(temp); 
                            this.sb.Append("</"+this.endTag+">");
                            ws = false;
                        }
                    }
                    else {
                        // must be just part of the CDATA block, so proceed.
                        this.sb.Append('<'); 
                        this.sb.Append(ch);
                        ws = false;
                    }
                } 
                else {
                    if (!this.current.IsWhitespace && ws) ws = false;
                    this.sb.Append(ch);
                }
                ch = this.current.ReadChar();
            }
            string value = this.sb.ToString();
            Push(null, XmlNodeType.CDATA, value);
            if (this.partial == '\0')
                this.partial = ' ';// force it to pop this CDATA next time in.
            return true;
        }

        void ExpandEntity(StringBuilder sb, char terminator) {
            char ch = this.current.ReadChar();
            if (ch == '#') {
                string charent = this.current.ExpandCharEntity();
                sb.Append(charent);
                ch = this.current.Lastchar;
            } 
            else {
                this.name.Length = 0;
                while (ch != Entity.EOF && 
                    (Char.IsLetter(ch) || ch == '_' || ch == '-')) {
                    this.name.Append(ch);
                    ch = this.current.ReadChar();
                }
                string name = this.name.ToString();
                if (this.dtd != null && name != "") {
                    Entity e = (Entity)this.dtd.FindEntity(name);
                    if (e != null) {
                        if (e.Internal) {
                            sb.Append(e.Literal);
                            if (ch != terminator) 
                                ch = this.current.ReadChar();
                            return;
                        } 
                        else {
                            Entity ex = new Entity(name, e.PublicId, e.Uri, this.current.Proxy);
                            e.Open(this.current, new Uri(e.Uri));
                            this.current = ex;
                            this.current.ReadChar();
                            return;
                        }
                    } 
                    else {
                        Log("Undefined entity '{0}'", name);
                    }
                }
                // Entity is not defined, so just keep it in with the rest of the
                // text.
                sb.Append("&");
                sb.Append(name);
                if (ch != terminator) {
                    sb.Append(ch);
                    ch = this.current.ReadChar();
                }
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets a value indicating whether the reader is positioned at the end of the stream.
		/// </summary>
		/// <value></value>
		/// <returns>true if the reader is positioned at the end of the stream; otherwise, false.</returns>
        public override bool EOF { 
            get {
                return this.state == State.Eof;
            }
        }

		/// <summary>
		/// When overridden in a derived class, changes the <see cref="P:System.Xml.XmlReader.ReadState"></see> to Closed.
		/// </summary>
        public override void Close() {
            if (this.current != null) {
                this.current.Close();
                this.current = null;
            }
            if (this.log != null) {
                this.log.Close();
                this.log = null;
            }
        }

		/// <summary>
		/// When overridden in a derived class, gets the state of the reader.
		/// </summary>
		/// <value></value>
		/// <returns>One of the <see cref="T:System.Xml.ReadState"></see> values.</returns>
        public override ReadState ReadState { 
            get {
                if (this.state == State.Initial) return ReadState.Initial;
                else if (this.state == State.Eof) return ReadState.EndOfFile;
                return ReadState.Interactive;
            }
        }

		/// <summary>
		/// When overridden in a derived class, reads the contents of an element or text node as a string.
		/// </summary>
		/// <returns>
		/// The contents of the element or an empty string.
		/// </returns>
		/// <exception cref="T:System.Xml.XmlException">An error occurred while parsing the XML. </exception>
        public override string ReadString() {
            if (this.node.NodeType == XmlNodeType.Element) {
                this.sb.Length = 0;
                while (Read()) {
                    switch (this.NodeType) {
                        case XmlNodeType.CDATA:
                        case XmlNodeType.SignificantWhitespace:
                        case XmlNodeType.Whitespace:
                        case XmlNodeType.Text:
                            this.sb.Append(this.node.Value);
                            break;
                        default:
                            return this.sb.ToString();
                    }
                }
                return this.sb.ToString();
            }
            return this.node.Value;
        }


		/// <summary>
		/// When overridden in a derived class, reads all the content, including markup, as a string.
		/// </summary>
		/// <returns>
		/// All the XML content, including markup, in the current node. If the current node has no children, an empty string is returned.If the current node is neither an element nor attribute, an empty string is returned.
		/// </returns>
		/// <exception cref="T:System.Xml.XmlException">The XML was not well-formed, or an error occurred while parsing the XML. </exception>
        public override string ReadInnerXml() {
            StringWriter sw = new StringWriter();
            XmlTextWriter xw = new XmlTextWriter(sw);
            xw.Formatting = Formatting.Indented;
            switch (this.NodeType) {
                case XmlNodeType.Element:
                    Read();
                    while (!this.EOF && this.NodeType != XmlNodeType.EndElement) {
                        xw.WriteNode(this, true);
                    }
                    Read(); // consume the end tag
                    break;
                case XmlNodeType.Attribute:
                    sw.Write(this.Value);
                    break;
                default:
                    // return empty string according to XmlReader spec.
                    break;
            }
            xw.Close();
            return sw.ToString();
        }

		/// <summary>
		/// When overridden in a derived class, reads the content, including markup, representing this node and all its children.
		/// </summary>
		/// <returns>
		/// If the reader is positioned on an element or an attribute node, this method returns all the XML content, including markup, of the current node and all its children; otherwise, it returns an empty string.
		/// </returns>
		/// <exception cref="T:System.Xml.XmlException">The XML was not well-formed, or an error occurred while parsing the XML. </exception>
        public override string ReadOuterXml() {
            StringWriter sw = new StringWriter();
            XmlTextWriter xw = new XmlTextWriter(sw);
            xw.Formatting = Formatting.Indented;
            xw.WriteNode(this, true);
            xw.Close();
            return sw.ToString();
        }

		/// <summary>
		/// When overridden in a derived class, gets the <see cref="T:System.Xml.XmlNameTable"></see> associated with this implementation.
		/// </summary>
		/// <value></value>
		/// <returns>The XmlNameTable enabling you to get the atomized version of a string within the node.</returns>
        public override XmlNameTable NameTable { 
            get {
                return this.nametable;
            }
        }

		/// <summary>
		/// When overridden in a derived class, resolves a namespace prefix in the current element's scope.
		/// </summary>
		/// <param name="prefix">The prefix whose namespace URI you want to resolve. To match the default namespace, pass an empty string.</param>
		/// <returns>
		/// The namespace URI to which the prefix maps or null if no matching prefix is found.
		/// </returns>
        public override string LookupNamespace(string prefix) {           
            return null;// there are no namespaces in SGML.
        }

		/// <summary>
		/// When overridden in a derived class, resolves the entity reference for EntityReference nodes.
		/// </summary>
		/// <exception cref="T:System.InvalidOperationException">The reader is not positioned on an EntityReference node; this implementation of the reader cannot resolve entities (<see cref="P:System.Xml.XmlReader.CanResolveEntity"></see> returns false). </exception>
        public override void ResolveEntity() {
            // We never return any entity reference nodes, so this should never be called.
            throw new InvalidOperationException("Not on an entity reference.");
        }

		/// <summary>
		/// When overridden in a derived class, parses the attribute value into one or more Text, EntityReference, or EndEntity nodes.
		/// </summary>
		/// <returns>
		/// true if there are nodes to return.false if the reader is not positioned on an attribute node when the initial call is made or if all the attribute values have been read.An empty attribute, such as, misc="", returns true with a single node with a value of String.Empty.
		/// </returns>
        public override bool ReadAttributeValue() {
            if (this.state == State.Attr) {
                this.state = State.AttrValue;
                return true;
            } 
            else if (this.state == State.AttrValue) {
                return false;
            }
            throw new InvalidOperationException("Not on an attribute.");
        }   

        void Validate(Node node) {
            if (this.dtd != null) {
                ElementDecl e = this.dtd.FindElement(node.Name);
                if (e != null) {
                    node.DtdType = e;
                    if (e.ContentModel.DeclaredContent == DeclaredContent.EMPTY) 
                        node.IsEmpty = true;
                }
            }
        }

        void ValidateAttribute(Node node, Attribute a) {
            ElementDecl e = node.DtdType;
            if (e != null) {
                AttDef ad = e.FindAttribute(a.Name);
                if (ad != null) {
                    a.DtdType = ad;
                }
            }
        }   

        void ValidateContent(Node node) {
            if (this.dtd != null) {
                // See if this element is allowed inside the current element.
                // If it isn't, then auto-close elements until we find one
                // that it is allowed to be in.                                  
                string name = this.nametable.Add(node.Name.ToUpper()); // DTD is in upper case
                int i = 0;
                int top = this.stack.Count-2;
                if (node.DtdType != null) { 
                    // it is a known element, let's see if it's allowed in the
                    // current context.
                    for (i = top; i>0; i--) {
                        Node n = (Node)this.stack[i];
                        if (n.IsEmpty) 
                            continue; // we'll have to pop this one
                        ElementDecl f = n.DtdType;
                        if (f != null) {
                            if (f.Name == this.dtd.Name)
                                break; // can't pop the root element.
                            if (f.CanContain(name, this.dtd)) {
                                break;
                            } 
                            else if (!f.EndTagOptional) {
                                // If the end tag is not optional then we can't
                                // auto-close it.  We'll just have to live with the
                                // junk we've found and move on.
                                break;
                            }
                        } 
                        else {
                            // Since we don't understand this tag anyway,
                            // we might as well allow this content!
                            break;
                        }
                    }
                }
                if (i == 0) {
                    // Tag was not found or is not allowed anywhere, ignore it and 
                    // continue on.
                }
                else if (i < top) {
                    Node n = (Node)this.stack[top];
                    if (i == top - 1 && name == n.Name) {
                        // e.g. p not allowed inside p, not an interesting error.
                    } else {
                        string closing = "";
                        for (int k = top; k >= i+1; k--) {
                            if (closing != "") closing += ",";
                            Node n2 = (Node)this.stack[k];
                            closing += "<"+n2.Name+">";
                        }
                        Log("Element '{0}' not allowed inside '{1}', closing {2}.", 
                            name, n.Name, closing);
                    }
                    this.state = State.AutoClose;
                    this.newnode = node;
                    Pop(); // save this new node until we pop the others
                    this.poptodepth = i+1;
                }
            }
        }
    }
}